import time,struct
from pyb import UART,Pin

pin0 = Pin("P0", Pin.IN, Pin.PULL_UP) #for test
pin1 = Pin("P1", Pin.OUT_PP, Pin.PULL_NONE) #for test

#public function
class myCustomErr(Exception):
    def __init__(self,message):
        self.message = message
        super().__init__(self.message)

class XFS5152(object):
    def __init__(self,usart,debug=False):
        self.usart = usart
        self.xfs_header = b'\xFD'
        self.debug = debug
        self.recog_status,self.recog_timout = 0,0
        self.encvoicebuf,self.encvoi_time = b'',0 #录音时长
        self.decPtr,self.decUnit = 0,0x2B
        
        self.encode_type={
            "gb2312":0x00,
            "gbk":0x01,
            "big5":0x02,
            "unicode":0x03
            }
        self.xfs_cmdlist={
            #语音合成功能支持的控制命令
            "play": 0x01, #语音合成
            "stop":0x02, #停止合成
            "pause": 0x03, #暂停合成
            "resume": 0x04, #恢复合成
            
            #查询和控制模式命令
            "state": 0x21, #该命令判断TTS芯片是否处在合成状态，返回0x4E表明芯片仍在合成中，返回0x4F表明芯片处于空闲状态
            "sleep": 0x88, #进入省电模式
            "weakup": 0xFF, #从省电模式返回正常工作模式
            
            "sendtobuf":0x31,#4K，共分 16 个区，区参数X=(0-15)，每个区的空间为 256 字节。 参数后直接跟文本（缓存时没有编码设置）
            "playbuf":0x32,#参数字节高4是播放次数，低4位是文本编码
            }
        self.xfs_acklist = {
            "init ok": b'\x4A', #初始化成功
            "cmd ok": b'\x41',  #收到正确的命令帧
            "cmd err": b'\x45', #收到不能识别命令帧
            "busy": b'\x4E',    #芯片忙碌状态--芯片处于合成文本状态
            "idle": b'\x4F'  #芯片空闲状态
            }
        self.voice_para={
            "8K":0x00, #采样率
            "16K":0x01,

            "level0":0x00, #比特率等级
            "level1":0x01,
            "level2":0x02,
            "level3":0x03,
            "level4":0x04,
            "level5":0x05,

            "volume0":0x00, #音量
            "volume1":0x01,
            "volume2":0x02,
            "volume3":0x03,
            "volume4":0x04,
            "volume5":0x05,
            "volume6":0x06,
            "volume7":0x07,
            "volume8":0x08, #自动增益控制
            }
        self.xfs_recoglist=['我在吃饭','我在修车','我在加油','正在休息','同意',
                       '不同意','我去','现在几点','今天几号','读信息',
                       '开始读','这是哪儿','打开广播','关掉广播','打开音乐',
                       '关掉音乐','再听一次','再读一遍','大声点','小声点',
                       '读短信','读预警信息','明天天气怎么样','紧急预警信息','开始',
                       '停止','暂停','继续读','确定开始','取消']

    def clearbuf(self):
        self.usart.read()

    def _send(self,data,opt=''):
        self.usart.write(data)
        if self.debug is True:
            print(opt+'-> ['+','.join([hex(x) for x in data])+']')
        return self._check_ready()

    def _recv(self):
        if self.usart.any() > 0:
            return self.usart.read()
        return None

    def _check_ready(self):
        res,timoutCnt = 0,0
        while True:
            time.sleep_us(500)
            timoutCnt += 1
            if timoutCnt > 50: #25ms
                res = -1
                break
            buf = self._recv()
            if buf is not None and buf == self.xfs_acklist['cmd ok']:
                break
            else:
                res = -2
        return res
    
    #excute 100ms interval...
    def check_idle(self):
        res = self._recv()
        if res is not None and self.xfs_acklist['idle'] in res:
            return True #idle
        return False #busy
    
    #utf8 to unicode
    def utf8_2_uni(self,utf): #3字节 b'\xe7\xa7\x91\xe5\xa4\xa7\xe8\xae\xaf\xe9\xa3\x9e'
        utf = utf.encode()
        cnt,unires = 0,bytearray(0)
        while cnt < len(utf):
            if int(utf[cnt]) < 0x80: #ascii
                unires += struct.pack('<H',utf[cnt]) 
                cnt += 1
            else:
                if utf[cnt]&0xF0 == 0xE0: #chinese
                    tmp = int((utf[cnt] & 0x0F)<<12) + int((utf[cnt+1] & 0x3F)<<6) + int(utf[cnt+2] & 0x3F)
                    unires += struct.pack('<H',tmp)
                    cnt += 3
        return unires
    
    def control(self,cmd):
        if cmd not in ['stop','pause','resume','state','sleep','wakeup']:
            raise myCustomErr('control Command Error...')
        buf = bytearray()
        buf += self.xfs_header
        buf += struct.pack('>H',1)
        buf += struct.pack('B',self.xfs_cmdlist[cmd])
        self._send(buf,'control')

    #test string:[gb2312 '科大讯飞'] b'\xFD\x00\x0A\x01\x00\xBF\xC6\xB4\xF3\xD1\xB6\xB7\xC9'
    def play(self,text,encode): #帧头1字节=0xfd -- 数据长度2字节 -- 数据(命令字 参数 文本)=n个字节<4K
        if len(text) > 4096: #error
            return -1
        if encode == 'unicode':
            text = self.utf8_2_uni(text)
        if type(text) == str:
            text = text.encode() #to bytes
        buf = bytearray()
        buf += self.xfs_header
        buf += struct.pack('>H',len(text)+2) #cmd+encode+data[0:4096]
        buf += struct.pack('2B',self.xfs_cmdlist['play'],self.encode_type[encode])
        buf += text.encode() if type(text) == str else text
        self._send(buf,'play')
        return 0
    
    #voice encode/decode
    def voice(self,cmd,*para):
        if cmd not in ['encode','decode','stop']:
            raise myCustomErr('voice Command Error...')
        if cmd == 'encode':
            self.encvoicebuf = b'' #ready for encode...
            self.encvoi_time = 0
        buf = bytearray()
        buf += self.xfs_header
        if cmd == 'stop':
            buf += struct.pack('>H',1)
            buf += b'\x44'
        else:
            samplerate,bitrate,volume = para
            buf += struct.pack('>H',4) #cmd+para1+para2+para3 参数1：采样率；参数2：设置比特率等级；参数3：设置音量
            buf += b'\x41' if cmd=='encode' else b'\x42' #command
            buf += struct.pack('3B',self.voice_para[samplerate],self.voice_para[bitrate],self.voice_para[volume])
        self._send(buf,'voice')

    #excute 500ms interval...
    def voice_check(self,_dir=0):  #dir=0 encode ; =1 decode
        res = -1
        if _dir == 0:
            self.encvoi_time += 500 #统计录音编码时长
        buf = self._recv()
        if buf is not None:
            try:
                if buf[0] == 0x41 and len(buf)>1 and _dir==1: #防止命令应答和识别结果一起返回
                    buf = buf[1:]
                frame,dath,datl,cmd,*encdat = buf
                if frame == 0xFC:
                    if cmd == 0x20: #发送启动编码命令
                        self.encvoicebuf += bytes(encdat[:-1])
                        if self.debug is True:
                            print('enc size[{}] {} {}ms'.format(len(encdat),len(self.encvoicebuf),self.encvoi_time))
                        res = 0
                    elif cmd == 0x21: #录音错误
                        if self.debug is True:
                            print('encode err.')
                        res = -2
                    elif cmd == 0x22: #录音完毕
                        if self.debug is True:
                            print('encode finished.')
                        res = 1
                    elif cmd == 0x23: #申请待解码数据【解码数据需按帧发送，mcu接收到xfs5152申请数据的请求后发送一帧数据】
                        buf = bytearray()
                        buf += self.xfs_header
                        length = self.decUnit*10 #1帧需要发送self.decUnit个字节，500ms需要一次发够多，xfs在后台慢慢解
                        buf += struct.pack('>H',length) #length
                        buf += b'\x43' #command
                        buf += self.encvoicebuf[self.decPtr:self.decPtr+length]
                        self._send(buf,'decode')

                        self.decPtr += length #write pointer move to next frame...
                        if self.decPtr > len(self.encvoicebuf):
                            self.voice('stop',self.decPtr)
                            print('end...')
                        if self.debug is True:
                            print('request for encode data...')
                    else:
                        pass
            except Exception as Err:
                print(Err)
        return res

    def recog(self,cmd):
        if cmd not in ['start','stop']:
            raise myCustomErr('recog Command Error...')
        buf = bytearray()
        buf += self.xfs_header
        buf += struct.pack('>H',1) #cmd
        buf += b'\x10' if cmd=='start' else b'\x1F'
        self._send(buf,'recog')
        self.recog_timout = 0 #clear

    #excute 500ms interval...
    def recog_check(self):
        res = -1
        self.recog_timout += 1
        buf = self._recv()
        if buf is not None:
            try:
                if buf[0] == 0x41 and len(buf)>1: #防止命令应答和识别结果一起返回
                    buf = buf[1:]
                frame,dath,datl,cmd,*content = buf
                if frame == 0xFC:
                    if cmd == 0x01: #返回识别结果ID--启动识别命令后，有识别结果产生时，最长检测到语音4秒会出结果或者产生拒识别
                        index = buf[4]
                        if self.debug is True:
                            print('recog ok -->',tts.xfs_recoglist[index])#buf[5:])
                        res = index
                    elif cmd == 0x02: #语音识别系统出现超时--启动识别命令后，检测无人说话5秒时
                        if self.debug is True:
                            print('recog timout.')
                        res = -2
                    elif cmd == 0x03: #语音识别系统出现拒识--机启动识别命令后，无结果时
                        if self.debug is True:
                            print('recog refuse.')
                        res = -3
                    elif cmd == 0x04: #语音识别系统出现内部错误--启动识别命令后，资源出错时
                        if self.debug is True:
                            print('recog internal err.')
                        res = -4
                    else:
                        pass
                    tts.recog('stop') #end of recognize...非必须
            except Exception as Err:
                print(Err)
        if self.recog_timout >= 12: #6s
            tts.recog('stop') #no data,force stop
            res = 0xFF #timeout
        return res

    def tone(self,idx):
        ranges = [(101,125),(201,225),(301,330)]
        if all(not (a<=idx<=b) for a,b in ranges) is True:
            raise myCustomErr('wrong Number...')
        self.play(b'sound'+str(idx).encode(),'gbk')

    def _tone_test_all(self):
        _type = 1 #1-信息提示音,2-铃声提示音,3-警报提示音
        while True:
            if _type == 1: #信息提示音(共 25 首)
                startIdx,endIdx = 101,125
            elif _type == 2:#铃声提示音(共 25 首)
                startIdx,endIdx = 201,225
            elif _type == 3:#警报提示音(共 30 首)
                startIdx,endIdx = 301,330
            idx = 0
            while startIdx <= endIdx:
                idx += 1
                self.play(b'sound'+str(startIdx).encode(),'gbk')
                print('tone=[{}] {}'.format(idx,startIdx))
                while True:
                    time.sleep(0.1)
                    buf = self._recv()
                    if buf == self.xfs_acklist['idle']:
                        startIdx += 1
                        time.sleep(1) #wait for a minute...
                        break
            _type += 1 #next
            if _type > 3:
                break

uart = UART(3, 115200,timeout=0, timeout_char=1,rxbuf=4096)
tts = XFS5152(uart,debug=True)  #tts.cmd_play(b'[f1][v1][m3][sound223]\xBF\xC6\xB4\xF3\xD3\x8D\xEF\x77[=ni3]','gbk')
tts.play('[v1][m51] 科大讯飞 2025年 [p500]','unicode')
pin1.value(1)
last = 0
while True:
    now = time.ticks_ms()
    if now - last >= 100:
        last = now #update
        res = tts.check_idle()
        if res is True:
            print('paly end')
            break 
        
#tts._tone_test_all() #ok
#tts.tone(101)

tts.play('[v1][m51] 开始识别','unicode')
while True:
    if tts.check_idle() == True:
        break

tts.recog('start')
while True:
    now = time.ticks_ms()
    if now - last >= 500:
        last = now #update
        res = tts.recog_check()
        if res >= 0:
            print('recog end.')
            tts.play('[v1][m51] 识别成功，结果为 [p300] '+tts.xfs_recoglist[res],'unicode')
            while True:
                if tts.check_idle() == True:
                    break
            while True:
                pass

''' #待优化，暂不折腾了，没需求。。。
tts.voice('encode','16K','level5','volume8')
last = 0
while 1:
    now = time.ticks_ms()
    if now - last >= 500:
        last = now #update
        res = tts.voice_check(0)
        if tts.encvoi_time > 4*1000:
            print('encode ok...')
            tts.voice('stop') #后面多出的数据不要
            tts.clearbuf() #clear buf
            break

time.sleep(1)
tts.voice('decode','16K','level5','volume8')
last = 0
while 1:
    now = time.ticks_ms()
    if now - last >= 100:
        last = now #update
        res = tts.voice_check(1)
'''